1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   */
19  package groovy.json.internal;
20  
21  import java.math.BigDecimal;
22  
23  import static groovy.json.internal.Exceptions.die;
24  import static groovy.json.internal.Exceptions.handle;
25  
26  /**
27   * @author Richard Hightower
28   */
29  public class CharScanner {
30  
31      protected static final int COMMA = ',';
32      protected static final int CLOSED_CURLY = '}';
33      protected static final int CLOSED_BRACKET = ']';
34      protected static final int LETTER_E = 'e';
35      protected static final int LETTER_BIG_E = 'E';
36      protected static final int DECIMAL_POINT = '.';
37      protected static final int ALPHA_0 = '0';
38      protected static final int ALPHA_1 = '1';
39      protected static final int ALPHA_2 = '2';
40      protected static final int ALPHA_3 = '3';
41      protected static final int ALPHA_4 = '4';
42      protected static final int ALPHA_5 = '5';
43      protected static final int ALPHA_6 = '6';
44      protected static final int ALPHA_7 = '7';
45      protected static final int ALPHA_8 = '8';
46      protected static final int ALPHA_9 = '9';
47      protected static final int MINUS = '-';
48      protected static final int PLUS = '+';
49      protected static final int DOUBLE_QUOTE = '"';
50      protected static final int ESCAPE = '\\';
51  
52      static final String MIN_LONG_STR_NO_SIGN = String.valueOf(Long.MIN_VALUE);
53      static final String MAX_LONG_STR = String.valueOf(Long.MAX_VALUE);
54      static final String MIN_INT_STR_NO_SIGN = String.valueOf(Integer.MIN_VALUE);
55      static final String MAX_INT_STR = String.valueOf(Integer.MAX_VALUE);
56  
57      private static double powersOf10[] = {
58              1.0,
59              10.0,
60              100.0,
61              1000.0,
62              10000.0,
63              100000.0,
64              1000000.0,
65              10000000.0,
66              100000000.0,
67              1000000000.0,
68              10000000000.0,
69              100000000000.0,
70              1000000000000.0,
71              10000000000000.0,
72              100000000000000.0,
73              1000000000000000.0,
74              10000000000000000.0,
75              100000000000000000.0,
76              1000000000000000000.0,
77      };
78  
79      public static boolean isDigit(int c) {
80          return c >= ALPHA_0 && c <= ALPHA_9;
81      }
82  
83      public static boolean isDecimalDigit(int c) {
84          return isDigit(c) || isDecimalChar(c);
85      }
86  
87      public static boolean isDecimalChar(int currentChar) {
88          switch (currentChar) {
89              case MINUS:
90              case PLUS:
91              case LETTER_E:
92              case LETTER_BIG_E:
93              case DECIMAL_POINT:
94                  return true;
95          }
96          return false;
97      }
98  
99      public static boolean hasDecimalChar(char[] chars, boolean negative) {
100         int index = 0;
101 
102         if (negative) index++;
103 
104         for (; index < chars.length; index++) {
105             switch (chars[index]) {
106                 case MINUS:
107                 case PLUS:
108                 case LETTER_E:
109                 case LETTER_BIG_E:
110                 case DECIMAL_POINT:
111                     return true;
112             }
113         }
114         return false;
115     }
116 
117     public static boolean isDigits(final char[] inputArray) {
118         for (int index = 0; index < inputArray.length; index++) {
119             char a = inputArray[index];
120             if (!isDigit(a)) {
121                 return false;
122             }
123         }
124         return true;
125     }
126 
127     public static char[][] splitExact(final char[] inputArray,
128                                       final char split, final int resultsArrayLength) {
129         /** Holds the results. */
130         char[][] results = new char[resultsArrayLength][];
131 
132         int resultIndex = 0;
133         int startCurrentLineIndex = 0;
134         int currentLineLength = 1;
135 
136         // TODO move unicode 0 to separate file to avoid doc parsing issues
137         char c = '\u0000';
138         int index = 0;
139 
140         for (; index < inputArray.length; index++, currentLineLength++) {
141             c = inputArray[index];
142             if (c == split) {
143 
144                 results[resultIndex] = Chr.copy(
145                         inputArray, startCurrentLineIndex, currentLineLength - 1);
146                 startCurrentLineIndex = index + 1; //skip the char
147 
148                 currentLineLength = 0;
149                 resultIndex++;
150             }
151         }
152 
153         if (c != split) {
154             results[resultIndex] = Chr.copy(
155                     inputArray, startCurrentLineIndex, currentLineLength - 1);
156             resultIndex++;
157         }
158 
159         int actualLength = resultIndex;
160         if (actualLength < resultsArrayLength) {
161             final int newSize = resultsArrayLength - actualLength;
162             results = __shrink(results, newSize);
163         }
164         return results;
165     }
166 
167     public static char[][] splitExact(final char[] inputArray,
168                                       final int resultsArrayLength, char... delims) {
169         /** Holds the results. */
170         char[][] results = new char[resultsArrayLength][];
171 
172         int resultIndex = 0;
173         int startCurrentLineIndex = 0;
174         int currentLineLength = 1;
175 
176         char c = '\u0000';
177         int index = 0;
178         int j;
179         char split;
180 
181         for (; index < inputArray.length; index++, currentLineLength++) {
182             c = inputArray[index];
183 
184             inner:
185             for (j = 0; j < delims.length; j++) {
186                 split = delims[j];
187 
188                 if (c == split) {
189                     results[resultIndex] = Chr.copy(
190                             inputArray, startCurrentLineIndex, currentLineLength - 1);
191                     startCurrentLineIndex = index + 1; //skip the char
192 
193                     currentLineLength = 0;
194                     resultIndex++;
195                     break inner;
196                 }
197             }
198         }
199 
200         if (!Chr.in(c, delims)) {
201             results[resultIndex] = Chr.copy(
202                     inputArray, startCurrentLineIndex, currentLineLength - 1);
203             resultIndex++;
204         }
205 
206         int actualLength = resultIndex;
207         if (actualLength < resultsArrayLength) {
208             final int newSize = resultsArrayLength - actualLength;
209             results = __shrink(results, newSize);
210         }
211         return results;
212     }
213 
214     public static char[][] split(final char[] inputArray,
215                                  final char split) {
216         /** Holds the results. */
217         char[][] results = new char[16][];
218 
219         int resultIndex = 0;
220         int startCurrentLineIndex = 0;
221         int currentLineLength = 1;
222 
223         char c = '\u0000';
224         int index = 0;
225 
226         for (; index < inputArray.length; index++, currentLineLength++) {
227             c = inputArray[index];
228             if (c == split) {
229                 if (resultIndex == results.length) {
230                     results = _grow(results);
231                 }
232 
233                 results[resultIndex] = Chr.copy(
234                         inputArray, startCurrentLineIndex, currentLineLength - 1);
235                 startCurrentLineIndex = index + 1; //skip the char
236 
237                 currentLineLength = 0;
238                 resultIndex++;
239             }
240         }
241 
242         if (c != split) {
243             results[resultIndex] = Chr.copy(
244                     inputArray, startCurrentLineIndex, currentLineLength - 1);
245             resultIndex++;
246         }
247 
248         int actualLength = resultIndex;
249         if (actualLength < results.length) {
250             final int newSize = results.length - actualLength;
251             results = __shrink(results, newSize);
252         }
253         return results;
254     }
255 
256     public static char[][] splitByChars(final char[] inputArray,
257                                         final char... delims) {
258         /** Holds the results. */
259         char[][] results = new char[16][];
260 
261         int resultIndex = 0;
262         int startCurrentLineIndex = 0;
263         int currentLineLength = 1;
264 
265         char c = '\u0000';
266         int index = 0;
267         int j;
268         char split;
269 
270         for (; index < inputArray.length; index++, currentLineLength++) {
271             c = inputArray[index];
272 
273             inner:
274             for (j = 0; j < delims.length; j++) {
275                 split = delims[j];
276                 if (c == split) {
277                     if (resultIndex == results.length) {
278                         results = _grow(results);
279                     }
280 
281                     results[resultIndex] = Chr.copy(
282                             inputArray, startCurrentLineIndex, currentLineLength - 1);
283                     startCurrentLineIndex = index + 1; //skip the char
284 
285                     currentLineLength = 0;
286                     resultIndex++;
287                     break inner;
288                 }
289             }
290         }
291 
292         if (!Chr.in(c, delims)) {
293             results[resultIndex] = Chr.copy(
294                     inputArray, startCurrentLineIndex, currentLineLength - 1);
295             resultIndex++;
296         }
297 
298         int actualLength = resultIndex;
299         if (actualLength < results.length) {
300             final int newSize = results.length - actualLength;
301             results = __shrink(results, newSize);
302         }
303         return results;
304     }
305 
306     public static char[][] splitByCharsFromToDelims(final char[] inputArray, int from, int to,
307                                                     final char... delims) {
308         /** Holds the results. */
309         char[][] results = new char[16][];
310 
311         final int length = to - from;
312 
313         int resultIndex = 0;
314         int startCurrentLineIndex = 0;
315         int currentLineLength = 1;
316 
317         char c = '\u0000';
318         int index = from;
319         int j;
320         char split;
321 
322         for (; index < length; index++, currentLineLength++) {
323             c = inputArray[index];
324 
325             inner:
326             for (j = 0; j < delims.length; j++) {
327                 split = delims[j];
328                 if (c == split) {
329                     if (resultIndex == results.length) {
330                         results = _grow(results);
331                     }
332 
333                     results[resultIndex] = Chr.copy(
334                             inputArray, startCurrentLineIndex, currentLineLength - 1);
335                     startCurrentLineIndex = index + 1; //skip the char
336 
337                     currentLineLength = 0;
338                     resultIndex++;
339                     break inner;
340                 }
341             }
342         }
343 
344         if (!Chr.in(c, delims)) {
345             results[resultIndex] = Chr.copy(
346                     inputArray, startCurrentLineIndex, currentLineLength - 1);
347             resultIndex++;
348         }
349 
350         int actualLength = resultIndex;
351         if (actualLength < results.length) {
352             final int newSize = results.length - actualLength;
353             results = __shrink(results, newSize);
354         }
355         return results;
356     }
357 
358     public static char[][] splitByCharsNoneEmpty(final char[] inputArray,
359                                                  final char... delims) {
360 
361         final char[][] results = splitByChars(inputArray, delims);
362         return compact(results);
363     }
364 
365     public static char[][] splitByCharsNoneEmpty(final char[] inputArray, int from, int to,
366                                                  final char... delims) {
367 
368         final char[][] results = splitByCharsFromToDelims(inputArray, from, to, delims);
369         return compact(results);
370     }
371 
372     public static char[][] compact(char[][] array) {
373         int nullCount = 0;
374         for (char[] ch : array) {
375             if (ch == null || ch.length == 0) {
376                 nullCount++;
377             }
378         }
379         char[][] newArray = new char[array.length - nullCount][];
380 
381         int j = 0;
382         for (char[] ch : array) {
383             if (ch == null || ch.length == 0) {
384                 continue;
385             }
386 
387             newArray[j] = ch;
388             j++;
389         }
390         return newArray;
391     }
392 
393     private static char[][] _grow(char[][] array) {
394         char[][] newArray = new char[array.length * 2][];
395         System.arraycopy(array, 0, newArray, 0, array.length);
396         return newArray;
397     }
398 
399     private static char[][] __shrink(char[][] array, int size) {
400         char[][] newArray = new char[array.length - size][];
401         System.arraycopy(array, 0, (char[][]) newArray, 0, array.length - size);
402         return newArray;
403     }
404 
405     public static boolean isLong(char[] digitChars) {
406         return isLong(digitChars, 0, digitChars.length);
407     }
408 
409     public static boolean isLong(char[] digitChars, int offset, int len) {
410         String cmpStr = digitChars[offset] == '-' ? MIN_LONG_STR_NO_SIGN : MAX_LONG_STR;
411         int cmpLen = cmpStr.length();
412         if (len < cmpLen) return true;
413         if (len > cmpLen) return false;
414 
415         for (int i = 0; i < cmpLen; ++i) {
416             int diff = digitChars[offset + i] - cmpStr.charAt(i);
417             if (diff != 0) {
418                 return (diff < 0);
419             }
420         }
421         return true;
422     }
423 
424     public static boolean isInteger(char[] digitChars) {
425         return isInteger(digitChars, 0, digitChars.length);
426     }
427 
428     public static boolean isInteger(char[] digitChars, int offset, int len) {
429         String cmpStr = (digitChars[offset] == '-') ? MIN_INT_STR_NO_SIGN : MAX_INT_STR;
430         int cmpLen = cmpStr.length();
431         if (len < cmpLen) return true;
432         if (len > cmpLen) return false;
433 
434         for (int i = 0; i < cmpLen; ++i) {
435             int diff = digitChars[offset + i] - cmpStr.charAt(i);
436             if (diff != 0) {
437                 return (diff < 0);
438             }
439         }
440         return true;
441     }
442 
443     public static int parseInt(char[] digitChars) {
444         return parseIntFromTo(digitChars, 0, digitChars.length);
445     }
446 
447     public static int parseIntFromTo(char[] digitChars, int offset, int to) {
448         try {
449             int num;
450             boolean negative = false;
451             char c = digitChars[offset];
452             if (c == '-') {
453                 offset++;
454                 negative = true;
455             }
456             if (negative) {
457                 num = (digitChars[offset] - '0');
458                 if (++offset < to) {
459                     num = (num * 10) + (digitChars[offset] - '0');
460                     if (++offset < to) {
461                         num = (num * 10) + (digitChars[offset] - '0');
462                         if (++offset < to) {
463                             num = (num * 10) + (digitChars[offset] - '0');
464                             if (++offset < to) {
465                                 num = (num * 10) + (digitChars[offset] - '0');
466                                 if (++offset < to) {
467                                     num = (num * 10) + (digitChars[offset] - '0');
468                                     if (++offset < to) {
469                                         num = (num * 10) + (digitChars[offset] - '0');
470                                         if (++offset < to) {
471                                             num = (num * 10) + (digitChars[offset] - '0');
472                                             if (++offset < to) {
473                                                 num = (num * 10) + (digitChars[offset] - '0');
474                                                 if (++offset < to) {
475                                                     num = (num * 10) + (digitChars[offset] - '0');
476                                                 }
477                                             }
478                                         }
479                                     }
480                                 }
481                             }
482                         }
483                     }
484                 }
485             } else {
486                 num = (digitChars[offset] - '0');
487                 if (++offset < to) {
488                     num = (num * 10) + (digitChars[offset] - '0');
489                     if (++offset < to) {
490                         num = (num * 10) + (digitChars[offset] - '0');
491                         if (++offset < to) {
492                             num = (num * 10) + (digitChars[offset] - '0');
493                             if (++offset < to) {
494                                 num = (num * 10) + (digitChars[offset] - '0');
495                                 if (++offset < to) {
496                                     num = (num * 10) + (digitChars[offset] - '0');
497                                     if (++offset < to) {
498                                         num = (num * 10) + (digitChars[offset] - '0');
499                                         if (++offset < to) {
500                                             num = (num * 10) + (digitChars[offset] - '0');
501                                             if (++offset < to) {
502                                                 num = (num * 10) + (digitChars[offset] - '0');
503                                                 if (++offset < to) {
504                                                     num = (num * 10) + (digitChars[offset] - '0');
505                                                 }
506                                             }
507                                         }
508                                     }
509                                 }
510                             }
511                         }
512                     }
513                 }
514 
515             }
516             return negative ? num * -1 : num;
517         } catch (Exception ex) {
518             return handle(int.class, ex);
519         }
520     }
521 
522     public static int parseIntFromToIgnoreDot(char[] digitChars, int offset, int to) {
523         int num;
524         boolean negative = false;
525         char c = digitChars[offset];
526         if (c == '-') {
527             offset++;
528             negative = true;
529         }
530 
531         c = digitChars[offset];
532         num = (c - '0');
533         offset++;
534 
535         for (; offset < to; offset++) {
536             c = digitChars[offset];
537             if (c != '.') {
538                 num = (num * 10) + (c - '0');
539             }
540         }
541 
542         return negative ? num * -1 : num;
543     }
544 
545     public static long parseLongFromToIgnoreDot(char[] digitChars, int offset, int to) {
546         long num;
547         boolean negative = false;
548         char c = digitChars[offset];
549         if (c == '-') {
550             offset++;
551             negative = true;
552         }
553 
554         c = digitChars[offset];
555         num = (c - '0');
556         offset++;
557 
558         for (; offset < to; offset++) {
559             c = digitChars[offset];
560             if (c != '.') {
561                 num = (num * 10) + (c - '0');
562             }
563         }
564 
565         return negative ? num * -1 : num;
566     }
567 
568     public static long parseLongFromTo(char[] digitChars, int offset, int to) {
569         long num;
570         boolean negative = false;
571         char c = digitChars[offset];
572         if (c == '-') {
573             offset++;
574             negative = true;
575         }
576 
577         c = digitChars[offset];
578         num = (c - '0');
579         offset++;
580 
581         long digit;
582 
583         for (; offset < to; offset++) {
584             c = digitChars[offset];
585             digit = (c - '0');
586             num = (num * 10) + digit;
587         }
588 
589         return negative ? num * -1 : num;
590     }
591 
592     public static long parseLong(char[] digitChars) {
593         return parseLongFromTo(digitChars, 0, digitChars.length);
594     }
595 
596     public static Number parseJsonNumber(char[] buffer) {
597         return parseJsonNumber(buffer, 0, buffer.length);
598     }
599 
600     public static Number parseJsonNumber(char[] buffer, int from, int to) {
601         return parseJsonNumber(buffer, from, to, null);
602     }
603 
604     public static final boolean isNumberDigit(int c) {
605         return c >= ALPHA_0 && c <= ALPHA_9;
606     }
607 
608     protected static boolean isDelimiter(int c) {
609         return c == COMMA || c == CLOSED_CURLY || c == CLOSED_BRACKET;
610     }
611 
612     public static Number parseJsonNumber(char[] buffer, int from, int max, int size[]) {
613         Number value = null;
614         boolean simple = true;
615         int digitsPastPoint = 0;
616 
617         int index = from;
618 
619         if (buffer[index] == '-') {
620             index++;
621         }
622 
623         boolean foundDot = false;
624         for (; index < max; index++) {
625             char ch = buffer[index];
626             if (isNumberDigit(ch)) {
627                 if (foundDot == true) {
628                     digitsPastPoint++;
629                 }
630             } else if (ch <= 32 || isDelimiter(ch)) {
631                 break;
632             } else if (ch == '.') {
633                 if (foundDot) {
634                     die("unexpected character " + ch);
635                 }
636                 foundDot = true;
637             } else if (ch == 'E' || ch == 'e' || ch == '-' || ch == '+') {
638                 simple = false;
639             } else {
640                 die("unexpected character " + ch);
641             }
642         }
643 
644         if (digitsPastPoint >= powersOf10.length - 1) {
645             simple = false;
646         }
647 
648         final int length = index - from;
649 
650         if (!foundDot && simple) {
651             if (isInteger(buffer, from, length)) {
652                 value = parseIntFromTo(buffer, from, index);
653             } else {
654                 value = parseLongFromTo(buffer, from, index);
655             }
656         } else if (foundDot && simple) {
657             BigDecimal lvalue;
658 
659             if (length < powersOf10.length) {
660                 if (isInteger(buffer, from, length)) {
661                     lvalue = new BigDecimal(parseIntFromToIgnoreDot(buffer, from, index));
662                 } else {
663                     lvalue = new BigDecimal(parseLongFromToIgnoreDot(buffer, from, index));
664                 }
665 
666                 BigDecimal power = new BigDecimal(powersOf10[digitsPastPoint]);
667                 value = lvalue.divide(power);
668             } else {
669                 value = new BigDecimal(new String(buffer, from, length));
670             }
671         } else {
672             value = new BigDecimal(new String(buffer, from, index - from));
673         }
674 
675         if (size != null) {
676             size[0] = index;
677         }
678 
679         return value;
680     }
681 
682     public static BigDecimal parseBigDecimal(char[] buffer) {
683         return parseBigDecimal(buffer, 0, buffer.length);
684     }
685 
686     public static BigDecimal parseBigDecimal(char[] buffer, int from, int to) {
687         return new BigDecimal(parseDouble(buffer, from, to));
688     }
689 
690     public static float parseFloat(char[] buffer, int from, int to) {
691         return (float) parseDouble(buffer, from, to);
692     }
693 
694     public static double parseDouble(char[] buffer) {
695         return parseDouble(buffer, 0, buffer.length);
696     }
697 
698     public static double parseDouble(char[] buffer, int from, int to) {
699         double value;
700         boolean simple = true;
701         int digitsPastPoint = 0;
702 
703         int index = from;
704 
705         if (buffer[index] == '-') {
706             index++;
707         }
708 
709         boolean foundDot = false;
710         for (; index < to; index++) {
711             char ch = buffer[index];
712             if (isNumberDigit(ch)) {
713                 if (foundDot == true) {
714                     digitsPastPoint++;
715                 }
716             } else if (ch == '.') {
717                 if (foundDot) {
718                     die("unexpected character " + ch);
719                 }
720                 foundDot = true;
721             } else if (ch == 'E' || ch == 'e' || ch == '-' || ch == '+') {
722                 simple = false;
723             } else {
724                 die("unexpected character " + ch);
725             }
726         }
727 
728         if (digitsPastPoint >= powersOf10.length - 1) {
729             simple = false;
730         }
731 
732         final int length = index - from;
733 
734         if (!foundDot && simple) {
735             if (isInteger(buffer, from, length)) {
736                 value = parseIntFromTo(buffer, from, index);
737             } else {
738                 value = parseLongFromTo(buffer, from, index);
739             }
740         } else if (foundDot && simple) {
741             long lvalue;
742 
743             if (length < powersOf10.length) {
744                 if (isInteger(buffer, from, length)) {
745                     lvalue = parseIntFromToIgnoreDot(buffer, from, index);
746                 } else {
747                     lvalue = parseLongFromToIgnoreDot(buffer, from, index);
748                 }
749 
750                 double power = powersOf10[digitsPastPoint];
751                 value = lvalue / power;
752             } else {
753                 value = Double.parseDouble(new String(buffer, from, length));
754             }
755         } else {
756             value = Double.parseDouble(new String(buffer, from, index - from));
757         }
758 
759         return value;
760     }
761 
762     public static int skipWhiteSpace(char[] array, int index) {
763         int c;
764         for (; index < array.length; index++) {
765             c = array[index];
766             if (c > 32) {
767                 return index;
768             }
769         }
770         return index;
771     }
772 
773     public static int skipWhiteSpace(char[] array, int index, final int length) {
774         int c;
775         for (; index < length; index++) {
776             c = array[index];
777             if (c > 32) {
778                 return index;
779             }
780         }
781         return index;
782     }
783 
784     public static char[] readNumber(char[] array, int idx) {
785         final int startIndex = idx;
786 
787         while (true) {
788             if (!CharScanner.isDecimalDigit(array[idx])) {
789                 break;
790             } else {
791                 idx++;
792                 if (idx >= array.length) break;
793             }
794         }
795 
796         return ArrayUtils.copyRange(array, startIndex, idx);
797     }
798 
799     public static char[] readNumber(char[] array, int idx, final int len) {
800         final int startIndex = idx;
801 
802         while (true) {
803             if (!CharScanner.isDecimalDigit(array[idx])) {
804                 break;
805             } else {
806                 idx++;
807                 if (idx >= len) break;
808             }
809         }
810 
811         return ArrayUtils.copyRange(array, startIndex, idx);
812     }
813 
814     public static int skipWhiteSpaceFast(char[] array) {
815         int c;
816         int index = 0;
817         for (; index < array.length; index++) {
818             c = array[index];
819             if (c > 32) {
820                 return index;
821             }
822         }
823         return index;
824     }
825 
826     public static int skipWhiteSpaceFast(char[] array, int index) {
827         char c;
828         for (; index < array.length; index++) {
829             c = array[index];
830             if (c > 32) {
831                 return index;
832             }
833         }
834         return index - 1;
835     }
836 
837     public static String errorDetails(String message, char[] array, int index, int ch) {
838         CharBuf buf = CharBuf.create(255);
839 
840         buf.addLine(message);
841 
842         buf.addLine("");
843         buf.addLine("The current character read is " + debugCharDescription(ch));
844 
845         buf.addLine(message);
846 
847         int line = 0;
848         int lastLineIndex = 0;
849 
850         for (int i = 0; i < index && i < array.length; i++) {
851             if (array[i] == '\n') {
852                 line++;
853                 lastLineIndex = i + 1;
854             }
855         }
856 
857         int count = 0;
858 
859         for (int i = lastLineIndex; i < array.length; i++, count++) {
860             if (array[i] == '\n') {
861                 break;
862             }
863         }
864 
865         buf.addLine("line number " + (line + 1));
866         buf.addLine("index number " + index);
867 
868         try {
869             buf.addLine(new String(array, lastLineIndex, count));
870         } catch (Exception ex) {
871             try {
872                 int start = index = (index - 10 < 0) ? 0 : index - 10;
873 
874                 buf.addLine(new String(array, start, index));
875             } catch (Exception ex2) {
876                 buf.addLine(new String(array, 0, array.length));
877             }
878         }
879         for (int i = 0; i < (index - lastLineIndex); i++) {
880             buf.add('.');
881         }
882         buf.add('^');
883 
884         return buf.toString();
885     }
886 
887     public static String debugCharDescription(int c) {
888         String charString;
889         if (c == ' ') {
890             charString = "[SPACE]";
891         } else if (c == '\t') {
892             charString = "[TAB]";
893         } else if (c == '\n') {
894             charString = "[NEWLINE]";
895         } else {
896             charString = "'" + (char) c + "'";
897         }
898 
899         charString = charString + " with an int value of " + ((int) c);
900         return charString;
901     }
902 }